/*
 * The marker is in a different layer above the perspective!
 */

package vivace.view;


import java.awt.Color;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.*;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import vivace.helper.GridHelper;
import vivace.model.*;

public class Marker extends JPanel implements Observer, AdjustmentListener   {

	private static final long serialVersionUID = -6011532832508862270L;
	private int width,height,position,scrollOffset,leftMargin,topMargin,bottomMargin;
	private JScrollPane relativeTo;
	private Point start;
	private Boolean isDragging;
	private JLabel markerImage;
	private JPanel markerLine;

	// Timer and it's listener, which is used to read the sequence's position
	private javax.swing.Timer updateTimer;
	private javax.swing.Action readSequencerPosition = new AbstractAction() {

		private static final long serialVersionUID = 1400493238103473566L;
		public void actionPerformed(ActionEvent e) {
			position = GridHelper.tickToXPosition(calculateTick());
			updatePosition(true);
		}
	};

	/**
	 * Constructor for maker
	 * @param relativeTo The scroll pane that the marker should adjust it's position after
	 * @param perspective The perspective where the marker is used
	 * @param leftMargin Margin (from left) in pixels
	 * @param rightMargin Margin (from right) in pixels
	 * @param topMargin Margin (from top) in pixels
	 * @param bottomMargin Margin (from bottom) in pixels
	 */
	public Marker( JScrollPane relativeTo, UI.Perspective perspective, int leftMargin, int rightMargin, int topMargin, int bottomMargin ){

		super(null); // Absolute positioning

		// Tell the model to notify about all changes 
		// for the currently active project
		App.addProjectObserver(this,App.Source.ALL);

		// Do some initialization
		this.relativeTo = relativeTo;
		relativeTo.getHorizontalScrollBar().addAdjustmentListener(this);
		position = 0;
		scrollOffset = 0;
		this.leftMargin = leftMargin;
		this.topMargin = topMargin;
		this.bottomMargin = bottomMargin;
		width = 30;
		height = 300;
		isDragging = false;
		setOpaque(false);
		updateTimer = new javax.swing.Timer(30,readSequencerPosition);
		initializeGUI();
	}

	/**
	 * Get height of marker
	 * @param h
	 */
	public void setHeight( int h ){
		height = h;
		markerLine.setSize(1,height-markerImage.getIcon().getIconHeight()-topMargin-bottomMargin);
	}

	/**
	 * initializeGUI for the marker
	 *
	 */
	private void initializeGUI(){

		// Create and position the marker's top image
		markerImage = new JLabel(); 
		markerImage.setIcon(new ImageIcon("resources/icon-marker3.png"));
		markerImage.setBounds(0,topMargin,markerImage.getIcon().getIconWidth(),markerImage.getIcon().getIconHeight());
		markerImage.addMouseMotionListener(new MarkerMouseMotionListener());
		markerImage.addMouseListener(new MarkerMouseListener());

		// Create and position the marker's line
		markerLine = new SelectionArea();
		markerLine.setSize(1,height-markerImage.getIcon().getIconHeight());
		markerLine.setBackground(Color.BLACK);
		markerLine.setBounds(width/2,markerImage.getIcon().getIconHeight()+topMargin,markerLine.getWidth(),markerLine.getHeight());

		// Add the components to the marker
		this.add(markerImage);
		this.add(markerLine);

	}

	@Override
	public void update(Observable o, Object arg) {

		// Check which type of action that was performed
		Action action = (Action) arg;

		// Then perform the desired updates depending on the action
		switch( action ){

		case SEQUENCER_STARTED:
			// Start the timer
			updateTimer.start();
			break;

		case SEQUENCER_STOPPED:
			// Stop the timer
			updateTimer.stop();
			break;

		case ZOOMLEVEL_CHANGED:
		case SEQUENCER_POSITION_CHANGED:
			position = GridHelper.tickToXPosition(calculateTick());
			updatePosition(false);
			break;
		}

	}
	// Takes the sequencer's position in ticks and take into account
	// the sequencer's latency to calculate the tick where the marker
	// really should be.
	private long calculateTick(){
		long realTick = App.Project.getSequencer().getTickPosition();
		return Math.max(realTick - App.Project.getLatencyInTicks(),0);
	}

	/**
	 * The scroller has been changed, so update markers position
	 */
	@Override
	public void adjustmentValueChanged(AdjustmentEvent evt) {
		scrollOffset = evt.getValue();
		updatePosition(false);
	}

	/**
	 * Update the markers position 
	 */
	private void updatePosition( boolean autoScrollIfNecessary ){

		// Declare the min and max value for the marker (-w/2 since the line are has this offset)
		int min = -width/2;
		int max = relativeTo.getHorizontalScrollBar().getMaximum();


		// Calculate the "view" position of the marker
		if( position < min ) {
			position = 0;
		} else if( position > max ) {
			position = max;
		}
		int viewX = position-scrollOffset-width/2+leftMargin;

		// Set the bounds of the marker
		this.setBounds(viewX,0,width,height);

		// If the marker is outside the view, hide it. We need to get the
		// width from the scroll pane and the view rect from the viewport for some reason
		if( viewX < min + leftMargin || viewX+width/2 > relativeTo.getViewport().getWidth()+leftMargin ){
			//if( viewX < min + leftMargin || viewX+width/2 > relativeTo.getHorizontalScrollBar().getWidth()+leftMargin ){

			markerImage.setVisible(false);
			markerLine.setVisible(false);

			// Autoscroll if enabled
			if( autoScrollIfNecessary ){
				// Calculate the step size used for autoscrolling (depends on whether the marker is dragged or not)
				int step = isDragging ? 10 : relativeTo.getHorizontalScrollBar().getWidth() / 2;
				relativeTo.getHorizontalScrollBar().setValue(relativeTo.getHorizontalScrollBar().getValue() + (viewX > leftMargin ? 1 : -1 ) * step);
			} 

		} else {
			markerImage.setVisible(true);
			markerLine.setVisible(true);
		}

	}

	// Mouse motion listener for the marker
	private class MarkerMouseMotionListener extends MouseMotionAdapter{

		@Override
		public void mouseDragged(MouseEvent e) {
			if( isDragging ) {
				drag(e);
			}
		}

	}

	// Mouse listener for the marker
	private class MarkerMouseListener extends MouseAdapter{

		public void mousePressed(MouseEvent e) {
			startDrag(e);		
		}

		public void mouseReleased(MouseEvent e) {
			stopDrag(e);
		}		

	}

	private void drag( MouseEvent e){
		// Calculate the dragged distance and update the track bar's position
		position += e.getPoint().x - start.x;
		updatePosition(true);
	}

	private void startDrag( MouseEvent e ){
		// Register the position where the drag started
		isDragging = true;
		start = e.getPoint();
	}

	private void stopDrag( MouseEvent e ){
		isDragging = false;
		// Update the model
		App.Project.setTickPosition( GridHelper.xPositionToTick(position));
	}

	public int getMarkerPosition(){
		return position;
	}


}
